#pragma once

#include "stdafx.h"

typedef struct T_Screen
{
	HMONITOR Monitor;
	RECT WorkArea;
	POINT CheckPoint;
	POINT Delta;
	MONITORINFO MonitorInfo;
	bool IsCheckPointSet;
	HWND Window;
	RECT Rect;

	T_Screen():IsCheckPointSet(false)
	{
	}

	T_Screen(const POINT& pt):CheckPoint(pt), IsCheckPointSet(true)
	{
	}
} Screen, *PScreen;

class WinSwapper
{
private:
#define MAX 10
	Screen _screens[MAX];
	PScreen _primaryScreen;
	bool _isDeltaCalced;
	typedef int t_id;
	t_id _screenCount;

	static bool GetScreen(const HMONITOR hMonitor, Screen& screen)
	{
		screen.Monitor = hMonitor;
		screen.MonitorInfo.cbSize = sizeof(MONITORINFO);
		GetMonitorInfo(screen.Monitor, &screen.MonitorInfo);

		screen.WorkArea = screen.MonitorInfo.rcWork;

		if (!screen.IsCheckPointSet)
		{
			screen.CheckPoint.x = (screen.WorkArea.left + screen.WorkArea.right) / 2;
			screen.CheckPoint.y = (screen.WorkArea.top + screen.WorkArea.bottom ) / 2;
			screen.IsCheckPointSet = true;
		}

		return screen.MonitorInfo.dwFlags == MONITORINFOF_PRIMARY;
	}

	static BOOL CALLBACK MonitorEnumCallBack(
		HMONITOR hMonitor,  // handle to display monitor
		HDC hdcMonitor,     // handle to monitor DC
		LPRECT lprcMonitor, // monitor intersection rectangle
		LPARAM dwData       // data
	)
	{
		WinSwapper* instance = (WinSwapper*)dwData;
		Screen screen;
		bool isPrimary(GetScreen(hMonitor, screen));
		instance->_screens[instance->_screenCount++] = screen;
		if (isPrimary)
			instance->_primaryScreen = &instance->_screens[instance->_screenCount - 1];
		return TRUE;
	}

	t_id Next(const t_id current) const
	{
		return current + 1 >= _screenCount ? 0 : current + 1;
	}

	PScreen Next(const PScreen current)
	{
		return (current + 1 > &_screens[_screenCount - 1]) ? &_screens[0] : current + 1;
	}

	void CalcDeltas()
	{
		for (t_id id = 0; id != _screenCount; ++id)
		{
			PScreen it = &_screens[id];
			PScreen next = &_screens[Next(id)];
			it->Delta.x = next->CheckPoint.x - it->CheckPoint.x;
			it->Delta.y = next->CheckPoint.y - it->CheckPoint.y;
		}
		_isDeltaCalced = true;
	}


public:

	WinSwapper(void):_isDeltaCalced(false), _screens(), _primaryScreen(), _screenCount(0)
	{
	}

	~WinSwapper(void)
	{
	}

	void SwapWin()
	{
		if(!_isDeltaCalced)
		{
			CalcDeltas();
		}

		// Find windows.
		for (t_id i = 0; i != _screenCount; ++i)
		{
			// Find window.
			_screens[i].Window = GetAncestor(WindowFromPoint(_screens[i].CheckPoint), GA_ROOTOWNER);
			// Get window position and size.
			GetWindowRect(_screens[i].Window, &_screens[i].Rect);
		}

		// Rotate windows.
		int count = 0;
		for (PScreen it = _primaryScreen; count != _screenCount; it = Next(it), ++count)
		{
			// Set window position.
			SetWindowPos(it->Window, NULL, 
				it->Rect.left + it->Delta.x,
				it->Rect.top + it->Delta.y,
				it->Rect.right - it->Rect.left,
				it->Rect.bottom - it->Rect.top,
				SWP_SHOWWINDOW);

			// Sets window to foreground.
			SetForegroundWindow(it->Window);
		}
	}

	void AddCheckPoint(POINT pt)
	{
		HMONITOR hMon = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL);
		if (hMon != NULL)
		{
			Screen screen(pt);
			GetScreen(hMon, screen);
			_screens[_screenCount++] = screen;
			if (_primaryScreen == NULL)
				_primaryScreen = &screen;
		}
	}

	void GenCheckPoints()
	{
		EnumDisplayMonitors(NULL, NULL, &MonitorEnumCallBack, (LPARAM)this);
		CalcDeltas();
	}
};
